/**
 * FileUploadService.java 
 * Copyright © 2015-3015, 杭州泊享网络科技有限公司
 * 
 * @author Liu Tao
 * @create 2014年10月12日
 */
package com.pshare.framework.service;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import com.pshare.constant.SeaxConstants;
import com.pshare.enums.FileType;
import com.pshare.framework.exception.SeaxRuntimeException;
import com.pshare.framework.exception.SeaxRuntimeException;
import com.pshare.tool.UpYunFileTool;
import com.pshare.tool.UpYunPicTool;
import com.pshare.util.FileUtils;
import com.pshare.util.RandomCodeUtils;
import com.pshare.util.SysPropertyUtils;
import com.pshare.util.video.FFmpegHandler;

/**
 * 文件上传处理业务层
 */
@Service
@Transactional
public class FileUploadService {

	private static final Logger logger = Logger.getLogger(FileUploadService.class);
	private static Set<String> picTypeSet = new HashSet<String>();
	private static Set<String> videoMimeTypeSet = new HashSet<String>();
	private static ExecutorService executorService = Executors.newCachedThreadPool();
	private static ExecutorService deleteService = Executors.newCachedThreadPool();

	private static final String LOCAL_PRE_HANDLE = SysPropertyUtils.getProperty("video.local.prehandle");
	private static final String TEMP_PATH = SysPropertyUtils.getProperty("file.temppath");
	private static final String FAIL_PATH = SysPropertyUtils.getProperty("file.failpath");

	static {
		picTypeSet.add("jpeg");
		picTypeSet.add("jpg");
		picTypeSet.add("png");
		picTypeSet.add("bmp");

		videoMimeTypeSet.add("x-msvideo");// avi
		videoMimeTypeSet.add("mpg");
		videoMimeTypeSet.add("wmv");
		videoMimeTypeSet.add("3gp");
		videoMimeTypeSet.add("mov");
		videoMimeTypeSet.add("mp4");// mp4
		videoMimeTypeSet.add("asf");
		videoMimeTypeSet.add("asx");
		videoMimeTypeSet.add("x-flv");// x-flv
	}

	/**
	 * @param is 文件流对象
	 * @param advanceFilePath 预先生成的文件保存地址
	 * @return
	 */
	public Map<Integer, String> uploadFile(InputStream is) {
		if (is == null) {
			logger.info("file upload faild,inputStream is null");
			return null;
		}
		Map<Integer, String> urlAndType = new HashMap<Integer, String>();

		// 根据文件流校验文件类型
		byte[] checkBytes = getBytesToCheck(is);
		String fileMimeType = FileUtils.getFileMimeType(checkBytes);
		String fileType = fileMimeType.split("/")[0];
		String fileExt = fileMimeType.split("/")[1];

		if (FileType.IMAGE.getName().equals(fileType)) {
			if (!picTypeSet.contains(fileExt)) {
				throw new SeaxRuntimeException("不支持的图片格式");
			}
			// 预先生成文件保存路径
			String generatedFilePath = generateFilePath(fileExt);
			// 上传图片
			uploadPicture(is, generatedFilePath);

			urlAndType.put(FileType.IMAGE.getCode(), generatedFilePath);
		} else if (FileType.VIDEO.getName().equals(fileType)) {
			if (!videoMimeTypeSet.contains(fileExt)) {
				throw new SeaxRuntimeException("不支持的视频格式");
			}
			// 视频统一采用mp4格式
			String generatedFilePath = generateFilePath("mp4");

			if ("true".equalsIgnoreCase(LOCAL_PRE_HANDLE)) {
				// 本地预处理
				uploadVideoAfterLocalProcessing(is, generatedFilePath, false);
			} else {
				// 分块上传
				uploadVieoFromFileBytes(is, generatedFilePath);
			}

			urlAndType.put(FileType.VIDEO.getCode(), generatedFilePath);
		} else {
			logger.info("file upload faild,file type error!");
		}
		return urlAndType;
	}

	/**
	 * 上传视频或图片
	 * 
	 * @param request
	 * @return List<String>
	 */
	public Map<String, Integer> uploadPicOrVideo(MultipartHttpServletRequest request) {
		Map<String, Integer> urlAndType = new HashMap<String, Integer>();
		try {
			MultiValueMap<String, MultipartFile> files = request.getMultiFileMap();
			if (files.size() == 0) {
				logger.warn("no files uploaded...");
				return urlAndType;
			}
			for (Iterator<String> iter = request.getFileNames(); iter.hasNext();) {
				int fileType;
				String url = null;
				LinkedList<MultipartFile> list = (LinkedList<MultipartFile>) files.get(iter.next());
				CommonsMultipartFile mFile = (CommonsMultipartFile) list.getFirst();
				if (mFile == null) {
					continue;
				}
				// 检查文件长度
				if (mFile.getSize() == 0) {
					logger.warn("文件大小有误");
					throw new SeaxRuntimeException("文件大小有误");
				}
				// 检查文件类型
				byte[] test = new byte[28];
				mFile.getInputStream().read(test, 0, 28);
				String fileMimeType = FileUtils.getFileMimeType(test);
				String fileTypeName = fileMimeType.split("/")[0];
				String extension = fileMimeType.split("/")[1];
				if (FileType.VIDEO.getName().equals(fileTypeName)) {
					if (!videoMimeTypeSet.contains(extension)) {
						throw new SeaxRuntimeException("不支持的视频格式");
					}
					fileType = FileType.VIDEO.getCode();
					url = uploadVideo(mFile, false);
				} else if (FileType.IMAGE.getName().equals(fileTypeName)) {
					if (!picTypeSet.contains(extension)) {
						throw new SeaxRuntimeException("不支持的图片格式");
					}
					fileType = FileType.IMAGE.getCode();
					url = uploadPictureToUpYun(mFile);
				} else {
					throw new SeaxRuntimeException("只能上传图片或者视频");
				}

				if (StringUtils.isNotBlank(url)) {
					urlAndType.put(url, fileType);
				}
			}
		} catch (SeaxRuntimeException e) {
			logger.error(e.getMessage(), e);
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new SeaxRuntimeException("上传失败");
		}
		return urlAndType;
	}

	/**
	 * 上传图片到又拍云
	 * 
	 * @param mFile
	 * @return String url
	 * @throws ServiceMsgException
	 */
	@Transactional
	public String uploadPictureToUpYun(CommonsMultipartFile mFile) {

		String fileName = mFile.getOriginalFilename();
		int index = fileName.lastIndexOf(".");
		if (index < 1) {
			logger.error("不支持的图片格式");
			throw new SeaxRuntimeException("不支持的图片格式");
		}
		String typeStr = fileName.substring(index + 1).toLowerCase();

		InputStream is = null;
		Calendar cal = Calendar.getInstance();
		int year = cal.get(Calendar.YEAR);
		int month = cal.get(Calendar.MONTH) + 1;
		String generatedName = RandomCodeUtils.getRandomNumOrLowerCaseStr(8);
		String filePath = SeaxConstants.SLASH + year + month + SeaxConstants.SLASH + generatedName + "." + typeStr;
		try {
			is = mFile.getInputStream();
			boolean result = false;
			result = UpYunPicTool.uploadFile(is, filePath);
			if (!result) {
				throw new SeaxRuntimeException("图片上传失败");
			}
		} catch (SeaxRuntimeException e) {
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new SeaxRuntimeException("图片上传失败");
		} finally {
			IOUtils.closeQuietly(is);
		}

		return filePath;
	}

	/**
	 * 删除图片
	 * 
	 * @param url
	 */
	public void deletePicture(String url) {
		if (StringUtils.isBlank(url)) {
			logger.warn("can't delete picture with blank url");
			return;
		}

		try {
			UpYunPicTool.deleteFile(url);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new SeaxRuntimeException("图片删除失败");
		}
	}

	/**
	 * 上传视频
	 * 
	 * @param mFile
	 * @return
	 */
	@Transactional
	public String uploadVideo(CommonsMultipartFile mFile, boolean hasLong) {
		String fileName = mFile.getOriginalFilename();
		int index = fileName.lastIndexOf(".");
		if (index < 1) {
			logger.error("不支持的视频格式");
			throw new SeaxRuntimeException("不支持的视频格式");
		}
		String typeStr = fileName.substring(index + 1).toLowerCase();

		Calendar cal = Calendar.getInstance();
		int year = cal.get(Calendar.YEAR);
		int month = cal.get(Calendar.MONTH) + 1;
		String generatedName = RandomCodeUtils.getRandomNumOrLowerCaseStr(8);
		String filePath = SeaxConstants.SLASH + year + month + SeaxConstants.SLASH + generatedName;

		InputStream is = null;
		try {
			is = mFile.getInputStream();
			// 本地预处理
			if ("true".equalsIgnoreCase(LOCAL_PRE_HANDLE)) {
				// 创建临时文件
				String tempPath = TEMP_PATH + File.separator + generatedName + "." + typeStr;
				FileUtils.copy(is, tempPath);
				executorService.execute(new Runnable() {
					@Override
					public void run() {
						String generatedFile = FFmpegHandler.process(tempPath, hasLong);
						logger.info("视频文件预处理" + tempPath + "-->" + generatedFile + "成功");
						String screenShotFile = FFmpegHandler.screenShot(generatedFile);
						logger.info("视频文件截图" + generatedFile + "-->" + screenShotFile + "成功");

						boolean videoResult = false;
						boolean screenResult = false;
						try {
							videoResult = UpYunFileTool.uploadFile(generatedFile, filePath + ".mp4");
							logger.info("上传视频到" + filePath + ".mp4 " + videoResult);

							screenResult = UpYunPicTool.uploadFile(new File(screenShotFile), filePath + ".png");
							logger.info("上传视频截图到" + filePath + ".png " + screenResult);
						} catch (Exception e) {
							logger.error(e.getMessage(), e);
						} finally {
							// 转移文件
							if (videoResult) {
								FileUtils.move(new File(generatedFile), FAIL_PATH + File.separator + year
										+ SeaxConstants.UNDERLINE + month + SeaxConstants.UNDERLINE + generatedName
										+ ".mp4");
							}
							if (screenResult) {
								FileUtils.move(new File(screenShotFile), FAIL_PATH + File.separator + year
										+ SeaxConstants.UNDERLINE + month + SeaxConstants.UNDERLINE + generatedName
										+ ".png");
							}

							// 删除临时文件
							FileUtils.delFile(screenShotFile);
							FileUtils.delFile(generatedFile);
							FileUtils.delFile(tempPath);
						}
					}
				});
			} else {
				// TODO 需要改成分块上传
				is = new ByteArrayInputStream(mFile.getBytes());
				boolean uploadResult = UpYunFileTool.uploadFile(is, filePath + "." + typeStr);
				executorService.execute(new Runnable() {
					@Override
					public void run() {
						if (uploadResult) {
							// TODO 验证未调通
							UpYunFileTool.handleVideo(filePath + "." + typeStr);
						}
					}
				});
			}
		} catch (SeaxRuntimeException e) {
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new SeaxRuntimeException("视频上传失败");
		} finally {
			IOUtils.closeQuietly(is);
		}
		return filePath + ".mp4";
	}

	private void uploadVideoAfterLocalProcessing(InputStream is, String generatedFilePath, boolean hasLong) {
		Calendar cal = Calendar.getInstance();
		int year = cal.get(Calendar.YEAR);
		int month = cal.get(Calendar.MONTH) + 1;
		// 不带扩展名的文件路径
		String filePathNoExt = generatedFilePath.substring(generatedFilePath.indexOf("."),
				generatedFilePath.length() + 1);
		try {
			// 创建临时文件
			String tempPath = TEMP_PATH + File.separator + generatedFilePath;
			FileUtils.copy(is, tempPath);
			executorService.execute(new Runnable() {
				@Override
				public void run() {
					String generatedFile = FFmpegHandler.process(tempPath, hasLong);
					logger.info("视频文件预处理" + tempPath + "-->" + generatedFile + "成功");
					String screenShotFile = FFmpegHandler.screenShot(generatedFile);
					logger.info("视频文件截图" + generatedFile + "-->" + screenShotFile + "成功");

					boolean videoResult = false;
					boolean screenResult = false;
					try {
						videoResult = UpYunFileTool.uploadFile(generatedFile, generatedFilePath);
						logger.info("上传视频到" + generatedFilePath + ".mp4 " + videoResult);

						screenResult = UpYunPicTool.uploadFile(new File(screenShotFile), filePathNoExt + ".png");
						logger.info("上传视频截图到" + filePathNoExt + ".png " + screenResult);
					} catch (Exception e) {
						logger.error(e.getMessage(), e);
					} finally {
						// 转移文件
						if (videoResult) {
							FileUtils.move(new File(generatedFile), FAIL_PATH + File.separator + year
									+ SeaxConstants.UNDERLINE + month + SeaxConstants.UNDERLINE + generatedFilePath);
						}
						if (screenResult) {
							FileUtils.move(new File(screenShotFile), FAIL_PATH + File.separator + year
									+ SeaxConstants.UNDERLINE + month + SeaxConstants.UNDERLINE + filePathNoExt
									+ ".png");
						}

						// 删除临时文件
						FileUtils.delFile(screenShotFile);
						FileUtils.delFile(generatedFile);
						FileUtils.delFile(tempPath);
					}
				}
			});
		} catch (Exception e) {
			logger.error("file upload faild:" + e.getMessage(), e);
			throw new SeaxRuntimeException("视频上传失败");
		} finally {
			IOUtils.closeQuietly(is);
		}
	}

	private void uploadVieoFromFileBytes(InputStream is, String generatedFilePath) {
		// TODO 需要改成分块上传
		ByteArrayInputStream byteArrIns;
		try {
			byteArrIns = new ByteArrayInputStream(FileUtils.input2byte(is));
			final boolean uploadResult = UpYunFileTool.uploadFile(byteArrIns, generatedFilePath);
			executorService.execute(new Runnable() {
				@Override
				public void run() {
					if (uploadResult)
						// TODO 验证未调通
						UpYunFileTool.handleVideo(generatedFilePath);
				}
			});
		} catch (Exception e) {
			logger.error("file upload faild:" + e.getMessage(), e);
			throw new SeaxRuntimeException("视频上传失败");
		} finally {
			IOUtils.closeQuietly(is);
		}
	}

	/**
	 * 重新上传文件
	 */
	public void reUploadFiles() {
		List<String> list = FileUtils.listFolderFiles(FAIL_PATH);
		for (String filePath : list) {
			int first = filePath.indexOf(SeaxConstants.UNDERLINE);
			int second = filePath.lastIndexOf(SeaxConstants.UNDERLINE);
			String year = filePath.substring(0, first);
			String month = filePath.substring(first + 1, second);
			String fileName = filePath.substring(second + 1, filePath.length());
			if (fileName.endsWith("x")) {
				fileName = fileName.substring(0, fileName.length());
			}
			String goal = File.separator + year + File.separator + month + File.separator + fileName;
			try {
				boolean uploadResult = UpYunFileTool.uploadFile(filePath, goal);
				logger.info("上传文件到" + goal + uploadResult);
				if (uploadResult)
					FileUtils.delFile(filePath);
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 获取文件流校验标本
	 * 
	 * @param is
	 * @return
	 */
	private byte[] getBytesToCheck(InputStream is) {
		byte[] checkBytes = new byte[28];
		try {
			is.read(checkBytes, 0, 28);
		} catch (IOException e) {
			logger.error("read inputStream to check faild" + e.getMessage());
		} finally {
			IOUtils.closeQuietly(is);
		}
		return checkBytes;
	}

	private void uploadPicture(InputStream is, String advanceFilePath) {
		boolean result = false;
		try {
			result = UpYunPicTool.uploadFile(is, advanceFilePath);
		} catch (IOException e) {
			logger.error("upload picture to upyun filed" + e.getMessage());
		} finally {
			IOUtils.closeQuietly(is);
		}
		if (!result) {
			logger.error("upload picture to upyun filed");
			throw new SeaxRuntimeException("图片上传失败");
		}
	}

	/**
	 * 预先生成文件路径
	 * 
	 * @param fileExt 文件后缀
	 * @return
	 */
	private static String generateFilePath(String fileExt) {
		Calendar cal = Calendar.getInstance();
		int year = cal.get(Calendar.YEAR);
		int month = cal.get(Calendar.MONTH) + 1;
		String generatedName = RandomCodeUtils.getRandomNumOrLowerCaseStr(8);
		String generatedFilePath = SeaxConstants.SLASH + year + month + SeaxConstants.SLASH + generatedName + "."
				+ fileExt;
		return generatedFilePath;
	}

	public void deletePhotos(ImgCallback callback) {
		deleteService.submit(callback);
	}

	/**
	 * 图片回调处理
	 */
	public class ImgCallback implements Callable<String> {

		protected Long id;
		protected Integer type;
		protected List<String> photos;
		


		public void setId(Long id) {
			this.id = id;
		}

		public void setType(Integer type) {
			this.type = type;
		}

		public void setPhotos(List<String> photos) {
			this.photos = photos;
		}

		@Override
		public String call() throws Exception {
			if (photos == null) {
				return null;
			}
			for (String photo : photos) {
				deletePicture(photo);
			}
			return null;
		}

	}

}
